class Ctrl {
  constructor() {
    this._ = $('#ctrl input[type=range]')
    this._img = document.querySelector('#img-viewer>img')

  }

  change() {
    let cst = this._[0].value
    let brt = this._[1].value
    let hue = this._[2].value
    let sfc = this._[3].value
    this._img.style.filter = `brightness(${brt}) contrast(${cst}) hue-rotate(${hue}deg) saturate(${sfc})`
  }

  reset = () => {
    this._[0].value = 1
    this._[1].value = 1
    this._[2].value = 0
    this._[3].value = 1
    this._img.style.filter = ''
  }
}

class Array2d extends Uint16Array {
  constructor(arraybuffer, opt) {
    super(arraybuffer)
    if (opt) {
      Object.assign(this, opt)
      this.min = this.reduce((a, b) => a < b ? a : b, 65535)
      this.max = this.reduce((a, b) => a > b ? a : b, 0)
      this.w = opt.info.spatial_pixels
      this.h = opt.info.spectral_pixels
    }
  }

  V(index) {
    let i = index, j;
    let r = new Uint16Array(this.h)
    for (j = 0; j < this.h; j++) {
      r[j] = this[i];
      i += this.w
    }
    return r
  }

  H(index) {
    let off = index * this.w;
    return this.slice(off, off + this.w)
  }
}

$CE = (v, options = {}) => {
  let el = document.createElementNS('http://www.w3.org/2000/svg', v)
  Object.keys(options).forEach(k => {
    el.setAttribute(k, options[k]);
  });
  return el;
}

class Im {
  constructor(id, p) {
    this.cvs = document.getElementById(id);
    this.svg = this.cvs.previousElementSibling;
    this.svg.appendChild(this._ax = $CE('g',));
    this.p = p;
    this.ctx = this.cvs.getContext('2d');
    this.cvs.onclick = this.click.bind(this);
  }

  _draw_tick(x, y, v, dx, dy) {
    this._ax.appendChild($CE('path',
      {d: `M${x},${y} L${x + dx},${y + dy}`, stroke: '#000', 'stroke-width': '1'}));
    let label = $CE('text', {
      'x': x + dx * 1.5,
      'y': y + dy * 1.5,
      'text-anchor': dx < 0 ? 'end' : (dx > 0 ? 'start' : 'middle'),
      'dominant-baseline': dy < 0 ? 'bottom' : (dy > 0 ? 'top' : 'middle')
    });
    label.textContent = v;
    this._ax.appendChild(label);
  }

  resize() {
    let h = this.cvs.height;
    let w = this.cvs.width;
    let dw = (this.cvs.offsetWidth - 2) / w;
    let dh = (this.cvs.offsetHeight - 2) / h;
    this._ax.remove();
    this.svg.appendChild(this._ax = $CE('g', {transform: 'translate(50,45)'}));
    let H = Math.round(2 ** ((Math.log2(h)) - 2));
    let i;
    for (i = 0; i <= h; i += H) {
      this._draw_tick(0, i * dh, i, -6, 0)
    }
    let W = Math.round(2 ** ((Math.log2(w)) - 3));
    for (i = 0; i <= w; i += W) {
      this._draw_tick(i * dw, 0, i, 0, -6)
    }

  }

  click(ev) {
    this.x = Math.round(ev.layerX * this.cvs.width / this.cvs.offsetWidth)
    this.y = Math.round(ev.layerY * this.cvs.height / this.cvs.offsetHeight)
    this.p.render();
  }

  shape(h, w) {
    this.cvs.height = h;
    this.cvs.width = w;
    this.resize()
  }

  draw_spec(v) {
    if (!v) {
      this.ctx.fillStyle = 'white'
      this.ctx.fillRect(0, 0, this.cvs.width, this.cvs.height)
      this._ax.remove()
      return
    }
    this.cvs.height = v.h
    this.cvs.width = v.w
    this.resize();
    var im = this.ctx.createImageData(v.w, v.h);
    var data = im.data;
    let o = this.equalize2d([v])[0];
    // let o = this.d4096to256([v])[0];
    for (let j = 0; j < v.length; j++) {
      let i = 4 * j;
      data[i + 2] = data[i + 1] = data[i] = o[j];        // R
      data[i + 3] = 255;      // A
    }
    this.ctx.putImageData(im, 0, 0);
  }

  draw_scan(v) {
    if (!v) {
      this.ctx.fillStyle = 'white'
      this.ctx.fillRect(0, 0, this.cvs.width, this.cvs.height)
      this._ax.remove()
      return
    }
    let H = this.cvs.height = v.w
    let p = this.p;
    let W = this.cvs.width = Math.ceil(p.d.length / 32.) * 32
    let im = this.ctx.createImageData(W, H)
    this.resize();
    let data = im.data;
    let o = this.equalize2d(p.d.map(i => i.H(p.S)))
    // let o = this.d4096to256(p.d.map(i => i.H(p.S)))
    let a, b = W * 4
    o.forEach((l, i) => {
      l.forEach((p, j) => {
        a = j * b + i * 4
        data[a] = data[a + 1] = data[a + 2] = p;
        data[a + 3] = 255
      })
    })
    this.ctx.putImageData(im, 0, 0);
  }

  d4096to256(data) {
    let h = new Uint8ClampedArray(4096).map((a, b) => b >> 4)
    return data.map(d => d.map(v => v < 4096 ? h[v] : 255))
  }

  equalize2d(data, L = 4096, ML = 255) {
    let i, histogram = new Array(L).fill(0), cdf = new Array(L).fill(0);
    // 计算直方图
    let size = 0
    data.forEach(d => {
      d.forEach(v => histogram[v]++);
      size += d.length
    })
    // 计算累积分布函数（CDF）
    cdf[0] = histogram[0];
    for (i = 1; i < L; i++) cdf[i] = cdf[i - 1] + histogram[i];
    for (i = 0; i < L; i++) histogram[i] = Math.round((cdf[i] / size) * ML);
    this._equa = histogram;
    return data.map(d => d.map(v => v < L ? histogram[v] : ML))
  }

  apply_equalize(data, _equa) {
    _equa = _equa || this._equa;
    return data.map(v => v < L ? _equa[v] : ML)
  }

}

class ImViewer {
  constructor(p,scan, spec, line, name) {
    this.p = p;
    let n = name.toLowerCase();
    this.$pix = $$(`#${n}p`)
    this.$chn = $$(`#${n}b`)
    this.$pix.onchange = (function () {
      this.P = parseInt(this.$pix.value);
      this.render()
    }).bind(this)
    this.$chn.onchange = (function () {
      this.S = parseInt(this.$chn.value);
      this.render()
    }).bind(this)
    this.scan = new Im(scan, this);
    this.spec = new Im(spec, this);
    this.P = 64;

    this.line = document.getElementById(line)
    this.name = name;
    this._3 = echarts.init(this.line)
    this._3.setOption({
      animation: false,
      grid: {top: 40, left: 60, bottom: 20, right: 30},
      title: {text: name, textStyle: {fontFamily: 'system-ui', fontSize: 16, fontWeight: 400}},
      xAxis: {type: 'value'},
      yAxis: {type: 'value'},
      series: {type: 'line', data: [[1, 2], [2, 3], [3, 2], [13, 1]]}
    })
    this.reset()
  }

  reset() {
    this.P = 64
    this.S = this.name === 'VI' ? 200 : 100;
    this.d = []
    this._3.setOption({title:{text:'光谱'},series:{data:[]}})
    this._3.resize()
    this.spec.draw_spec()
    this.scan.draw_scan()

  }

  add_frame(v, render = true) {
    this.L = this.d.length;
    if (this.d.length == 0) {
      this.P = Math.floor(parseInt(v.info.spatial_pixels) / 2);
    }
    this.d.push(v);
    if (render) this.render(v)
  }

  render(v) {
    if (!this.d.length && !v) return
    v = v || this.d[this.L];
    this._3.setOption({
      title: {text: `光谱 ${this.name} FRAME:${this.L} pix:${this.P} SP:${this.S}`},
      series: {data: Array.from(v.V(this.P)).map((j, i) => [i, j])}
    })
    this._3.resize();
    this.spec.draw_spec(v)
    this.scan.draw_scan(v)
  }

  set L(v) {
    this.scan.x = v
  }

  get L() {
    let t = this.scan.x
    this.L = t
    return t
  }

  set P(v) {
    this.$pix.textContent = this.spec.x = this.$pix.value = v
  }

  get P() {
    let t = this.spec.x
    this.P = t
    return t
  }

  get S() {
    let t = this.spec.y
    this.S = t
    return t
  }

  set S(v) {
    this.$chn.textContent = this.spec.y = this.$chn.value = v
  }

  cal(v, index) {
    this.d.push(v)
    this._3.setOption({
      title: {text: `光谱图 ${this.name} 定标:${index} pix:${this.P} SP:${this.S}`},
      series: {data: Array.from(v.V(this.P)).map((j, i) => [i, j])}
    })
    this._3.resize()
    this.spec.draw_spec(v)
  }

  renderFrame(i) {
    i = parseInt(i)
    this.p.params(this.p.frames[i])
    this.L = i
    this.render()
  }

}


const WKM = {
  VI11: "待机/可见",
  VI22: "被动扫描/可见",
  VI33: "主动扫描/可见",
  VI44: "内定标/可见",
  IR11: "待机/短中波",
  IR22: "被动扫描/短中波",
  IR33: "主动扫描/短中波",
  IR44: "内定标/短中波"
}
const PARAMS = {
  work_mode:{0x11:"待机",0x22:"被动扫描",0x33:"主动扫描",0x44:"内定标"},
  channel:{0x01:"可见近红外",0x02:"短中波"},
  target: {0x00: "星下", 0x01: "黑体", 0x02: "积分球", 0x03: "光谱定标灯"},
  compression_mode: {0x00: "未压缩", 0x01: "无损", 0x02: "近无损1", 0x03: "近无损"},
  frame_rate_setting: {0x00: "8Hz", 0x01: "1Hz", 0x02: "0.125Hz", 0x03: "32Hz累加模式"},
  row_column_priority: {0: "行优先", 1: "列优先"},
  time_accumulation_param: {0x00: "不累加", 0x01: "4次累加", 0x02: "8次累加"},
  integrating_sphere_enable:{0:"关",1:"开"},
  spectral_calibration_lamp_enable:{0:"关",1:"开"},
}

const params = [
  {n: "帧时间", k: "st", v: BJT},
  {n: "同步头", k: "sync_header", v: new RawFmt('8X').X, r: '8X'},
  {n: "工作模式", k: "work_mode", v: (v => PARAMS.work_mode[v]),r: '8X'},
  {n: "通道", k: "channel",v: (v => PARAMS.channel[v]), r: '8X'},
  {n: "空间像元数", k: "spatial_pixels", r: '8X'},
  {n: "光谱像元数", k: "spectral_pixels", r: '8X'},
  {n: "有效科学数据长度", k: "valid_data_length", r: '8X'},
  {n: "目标", k: "target", v: (v => PARAMS.target[v]), r: '8X'},
  {n: "帧计数", k: "frame_count", r: '8X'},
  {n: "指向角度", k: "pointing_angle", v: XFMT_Fixed(), r: '8X'},
  {n: "压缩模式", k: "compression_mode", v: (v => PARAMS.compression_mode[v]), r: '8X'},
  {n: "帧频设置", k: "frame_rate_setting", v: (v => PARAMS.frame_rate_setting[v]), r: '8X'},
  {n: "可见积分时间", k: "visible_integration_time", r: '8X'},
  {n: "中波积分时间", k: "short_midwave_integration_time", r: '8X'},
  {n: "定标电位器角度", k: "calibration_potentiometer_angle", v: XFMT_Fixed(), r: '8X'},
  {n: "短中波探测器温度（粗）", k: "short_midwave_detector_temp_coarse", v: XFMT_Fixed(), r: '8X'},
  {n: "短中波探测器温度（精）", k: "short_midwave_detector_temp_fine", v: XFMT_Fixed(), r: '8X'},
  {n: "可见探测器温度", k: "visible_detector_temp", v: XFMT_Fixed(), r: '8X'},
  {n: "可见光谱仪框架温度", k: "visible_spectrometer_frame_temp", v: XFMT_Fixed(), r: '8X'},
  {n: "望远镜框架温度", k: "telescope_frame_temp", v: XFMT_Fixed(), r: '8X'},
  {n: "可见光谱仪杜瓦窗口温度", k: "visible_spectrometer_dewar_window", v: XFMT_Fixed(), r: '8X'},
  {n: "短中波光谱仪框架温度", k: "short_midwave_spectrometer_frame_temp", v: XFMT_Fixed(), r: '8X'},
  {n: "共狭缝温度", k: "common_slit_temp", v: XFMT_Fixed(), r: '8X'},
  {n: "辐射板温度", k: "radiator_temp", v: XFMT_Fixed(), r: '8X'},
  {n: "积分球温度", k: "integrating_sphere_temp", v: XFMT_Fixed(), r: '8X'},
  {n: "光谱定标器温度", k: "spectral_calibrator_temp", v: XFMT_Fixed(), r: '8X'},
  {n: "黑体温度", k: "black_body_temp", v: XFMT_Fixed(), r: '8X'},
  {n: "积分球使能状态", k: "integrating_sphere_enable",v: (v => PARAMS.integrating_sphere_enable[v]), r: '8X'},
  {n: "光谱定标灯使能状态", k: "spectral_calibration_lamp_enable",v: (v => PARAMS.spectral_calibration_lamp_enable[v]), r: '8X'},
  {n: "采集时间码", k: "acquisition_time_code", v: BJT},
  {n: "行列优先区分", k: "row_column_priority", v: (v => PARAMS.row_column_priority[v]), r: '8X'},
  {n: "执行机构角度（慢）", k: "actuator_angle_slow", v: XFMT_Fixed(), r: '8X'},
  {n: "时间维累加计数", k: "time_accumulation_param", v: (v => PARAMS.time_accumulation_param[v]), r: '8X'},

]

class Main extends Realtime {
  constructor() {
    super('AVIRIS', $STATION, params)
    this.VI = new ImViewer(this,'vscan', 'vspec', 'vline', 'VI')
    this.IR = new ImViewer(this, 'iscan', 'ispec', 'iline', 'IR')
    window.onresize = this.resize.bind(this)
    this.connect()
    this.$modes = $('#modes').change(this.stop.bind(this));
    this.$frmr.onchange = this.onFrmr.bind(this)
    this.$imgViwer = $$('#img_viewer')
    this.$day.onchange = this.onDay.bind(this)
    this.load_frame_tasks=[]
  }
  async onDay(){
    this.close()
    this.resize()
    delete this._frames_loader;
    this.stopLoadFrameTasks()
    this.resetContent()
    await this.load_task()
    await this.load()
  }

  onFrmr() {
    let m = this.mode
    let frameIdx = this.$frmr.value
    if(!m){
      this.params(this.frames[frameIdx])
      return;
    }
    if(m.startsWith('VI'))  this.VI.renderFrame(frameIdx)
    if(m.startsWith('IR')) this.VI.renderFrame(frameIdx)
  }
  stopLoadFrameTasks(){
    this.load_frame_tasks.forEach(v=> clearTimeout(v))
    this.load_frame_tasks = []
    this.sleep()
  }
  async stop() {
    this.close()
    delete this._frames_loader;
    console.info('aviris stopLoadFrame begin')
    this.stopLoadFrameTasks()
    this.filter_frames(this.mode)
    console.info('aviris resetContent begin')
    this.resetContent()
     console.info('aviris load begin')
    if (this.frames.length > 1) {
      await this.load()
    }
  }

  resize() {
    this.VI._3.resize()
    this.IR._3.resize()
  }

  async onTask() {
    this.resize()
    await super.onTask();
    delete this._frames_loader;
    this.stopLoadFrameTasks()
    this.frame_index = this.frame_max =0
    await this.load()

  }

  async newtask(v) {
    console.log('Task new start')
    delete this._frames_loader;
    this.stopLoadFrameTasks()
    await super.newtask(v);
    this.IR.reset();
    this.VI.reset();
    this.st_frames()
    console.log('Task new end')
  }
  filter_frames(mode) {
    super.filter_frames(mode)
    this.st_frames()
  }

  st_frames() {
    let st = {};
    let selected = this.$modes.val()
    this.frames_ori.forEach(v => {
      st[v.mode] = 1 + (st[v.mode] || 0)
    })
      // "": "不限",
     let kwmOpt =  Object.keys(WKM).map(v => `<option value="${v}">${WKM[v]} :   ${v ? (st[v] || '-') : ''}</option>`).join('')
    this.$modes.html(this.ws? ('<option value="">不限</option>')+kwmOpt: kwmOpt)
    if(selected){
      this.$modes.val(selected)
    }else{
      this.$modes.selectedIndex = 0
    }
    let m = this.mode
    this.frames = m ? this.frames_ori.filter(v=> v.mode==m) : this.frames_ori
    this.frame_max = this.frames.length-1
    this.resetViewer()
  }

  async newframe(v) {
    console.log('Frame new start')
    await this.frame(v);
    console.log('Frame new start')
  }
  realtime() {
    this.close()
    this.reset()
    this.$day.value = ''
    $([this.$task,this.$mode]).html('')
    this.resetViewer()
    this.sleep()
    this.resetContent()
    super.realtime();
  }

  async history() {
    await super.history()
    await this.load_frame()
    await this.load()
  }

  async load() {
    this._frames_loader = 1
    this.load_frame_tasks = []
    this.frame_index = this._i = 0
    console.info("aviris reclearcontent begin")
    this.resetContent()
    await this.load_frame()
  }
  resetContent(){
    this.VI.reset()
    this.IR.reset()
    this.$info.innerHTML = ''
    this.resetFrmInput()
    this.resize()
  }

  async load_frame() {
    let v = this.frames[this._i];
    if (v === undefined) return this.render()

    if (this._frames_loader) {
      // console.info("aviris render frame begin")
      await this.frame(v)
      this._i++;
      this.frame_index = this._i
      let out = setTimeout(this.load_frame.bind(this), 1)
      this.load_frame_tasks.push(out)
      this._frames_loader = out
      // console.info("aviris render frame end")
    }
  }

  sleep(){
    let start = Date.now()
    while (Date.now() - start > 2000) {
    }
  }

  async frame(v) {
    if (this.mode && this.mode !== v.mode) return
    // console.info("aviris render frame do begin")
    this.st_frames()
    let m = v.mode.slice(0, 2)
    try{
      let bf = await $api(`omsp.bindata?t=${this.cur_task}&offset=${v.frm[0]}&size=${v.frm[1]}`, null, v => v.arrayBuffer())
      if (v.mode.endsWith('44')) {
        this[m].cal(new Array2d(bf, v), v.name)
      }else{
        this[m].add_frame(new Array2d(bf, v))
      }
      this.params(v)
    }finally{
      // this.resetViewer()
      // console.info("aviris render frame do begin")
    }
  }
  resetViewer(){
    let selectedM = this.mode
    if (this.ws) {
      this.VI.scan.cvs.parentNode.classList.remove('unscan')
      this.IR.scan.cvs.parentNode.classList.remove('unscan')
    }
     if(selectedM==undefined || selectedM==null || selectedM=='' || this.ws) {
        this.$imgViwer.className = 'V'
        return;
      }
     let m = selectedM.slice(0,2);

      if (selectedM.endsWith('44')) {
        this[m].scan.cvs.parentNode.classList.add('unscan')
      }else{
        this[m].scan.cvs.parentNode.classList.remove('unscan')
      }
      if(selectedM.startsWith('VI'))  this.$imgViwer.className = 'V1'
      if(selectedM.startsWith('IR')) this.$imgViwer.className = 'V2'
  }
  render() {
    this.IR.render()
    this.VI.render()
  }

}


main = new Main()
main.realtime()

